﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.IO;
using System.Globalization;
using Elentok.Common.Helpers;

namespace Elentok.Common
{
  /// <summary>
  /// Defines the most recent files service
  /// </summary>
  public interface IRecentFilesService
  {
    /// <summary>
    /// Gets the maximal amount of files to keep
    /// </summary>
    int MaxFilesToKeep { get; }

    /// <summary>
    /// Gets a collection of the files in the MRU
    /// </summary>
    ReadOnlyObservableCollection<string> Files { get; }

    /// <summary>
    /// Adds a file to the top of the list,
    /// <para>
    /// (if the file already exists it will be moved to the top of the list)
    /// </para>
    /// <para>
    /// (if the new size of the list is more than <see cref="MaxFilesToKeep"/> the list will be trimmed)
    /// </para>
    /// </summary>
    /// <param name="fileName"></param>
    void AddFile(string fileName);

    /// <summary>
    /// Tries to open the given file (by raising the "OpenFileRequested" event)
    /// <para>
    /// If successful, puts the file at the top of the list and returns true
    /// </para>
    /// <para>
    /// If unsuccessful, returns false
    /// </para>
    /// </summary>
    /// <param name="fileName"></param>
    bool OpenFile(string fileName);

    /// <summary>
    /// This event will be raised whenever someone calls the 'OpenFile' method
    /// </summary>
    event EventHandler<OpenFileRequestedEventArgs> OpenFileRequested;
  }

  /// <summary>
  /// Event arguments for the 'OpenRecentFileRequested' event
  /// </summary>
  public class OpenFileRequestedEventArgs : EventArgs
  {
    /// <summary>
    /// Gets the filename that was requested
    /// </summary>
    public string FileName { get; private set; }

    /// <summary>
    /// Gets or sets if the file was opened successfully (defaults to 'false')
    /// </summary>
    public bool Success { get; set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="OpenFileRequestedEventArgs"/> class.
    /// </summary>
    /// <param name="fileName"></param>
    public OpenFileRequestedEventArgs(string fileName)
    {
      this.FileName = fileName;
      this.Success = false;
    }
  }

  /// <summary>
  /// This class helps manage an MRU (Most-Recently-Used) files list.
  /// </summary>
  public sealed class RecentFilesService : IRecentFilesService
  {
    #region Ctors

    /// <summary>
    /// Initialize a new instance of the <see cref="RecentFilesService"/> class
    /// </summary>
    /// <param name="maxFilesToKeep">The maximal amount of files to keep in the list</param>
    /// <param name="mruFile">Use an alternate mru file (and not the default)</param>
    public RecentFilesService(int maxFilesToKeep, string mruFile)
    {
      this.MaxFilesToKeep = maxFilesToKeep;
      this.Files = new ReadOnlyObservableCollection<string>(files);

      if (string.IsNullOrEmpty(mruFile))
      {
        string dir = SystemHelper.GetUserSettingsPath(true);
        this.mruFilePath = Path.Combine(dir, "MostRecentFiles.txt");
      }
      else
        this.mruFilePath = mruFile;

      this.LoadFilesFromMruFile();
    }

    /// <summary>
    /// Initialize a new instance of the <see cref="RecentFilesService"/> class
    /// <para>
    /// Uses the default location for the MRU file (%AppData%\{AssemblyName}\MostRecentFiles.txt)
    /// </para>
    /// </summary>
    /// <param name="maxFilesToKeep">The maximal amount of files to keep in the list</param>
    public RecentFilesService(int maxFilesToKeep) : this(maxFilesToKeep, null) { }

    /// <summary>
    /// Initialize a new instance of the <see cref="RecentFilesService"/> class
    /// <para>
    /// Uses the default <see cref="MaxFilesToKeep"/> value
    /// </para>
    /// <para>
    /// Uses the default location for the MRU file (%AppData%\{AssemblyName}\MostRecentFiles.txt)
    /// </para>
    /// </summary>
    public RecentFilesService() : this(DefaultMaxFilesToKeep) { }

    #endregion

    #region Public Properties & Consts

    /// <summary>
    /// The default number of files to keep in the MRU
    /// </summary>
    public const int DefaultMaxFilesToKeep = 8;
    
    #endregion

    #region Private Fields

    private string mruFilePath;

    private ObservableCollection<string> files = new ObservableCollection<string>();
    
    #endregion

    #region Helper Methods

    /// <summary>
    /// Loads the files from the MRU file
    /// </summary>
    private void LoadFilesFromMruFile()
    {
      if (!File.Exists(this.mruFilePath))
        return;

      using (StreamReader reader = new StreamReader(this.mruFilePath, Encoding.UTF8))
      {
        while (!reader.EndOfStream)
        {
          string file = reader.ReadLine();
          if (!string.IsNullOrEmpty(file))
            this.files.Add(file.Trim());
        }
        reader.Close();
      }
    }

    /// <summary>
    /// Saves the MRU list to file
    /// </summary>
    private void SaveFilesToMruFile()
    {
      using (StreamWriter writer = new StreamWriter(this.mruFilePath, false, Encoding.UTF8))
      {
        foreach (string file in files)
        {
          writer.WriteLine(file);
        }
        writer.Close();
      }
    }
    

    /// <summary>
    /// Returns the index of the given file in the files list
    /// (uses a case-insensitive search to find the file in the list).
    /// Returns -1 if the file is not in the list.
    /// </summary>
    /// <param name="file"></param>
    /// <returns></returns>
    public int IndexOf(string file)
    {
      file = file.ToUpper(CultureInfo.CurrentCulture);

      for (int i = 0; i < this.files.Count; i++)
      {
        if (this.files[i].ToUpper(CultureInfo.CurrentCulture).Equals(file))
          return i;
      }

      return -1;
    }

    #endregion

    #region IRecentFilesManager Members

    /// <summary>
    /// Gets the maximal amount of files to keep
    /// </summary>
    public int MaxFilesToKeep { get; private set; }

    /// <summary>
    /// Gets a collection of the files in the MRU
    /// </summary>
    public ReadOnlyObservableCollection<string> Files { get; private set; }

    /// <summary>
    /// Adds a file to the top of the list,
    /// <para>
    /// (if the file already exists it will be moved to the top of the list)
    /// </para>
    /// <para>
    /// (if the new size of the list is more than <see cref="MaxFilesToKeep"/> the list will be trimmed)
    /// </para>
    /// </summary>
    /// <param name="fileName"></param>
    public void AddFile(string fileName)
    {
      int index = IndexOf(fileName);
      if (index != -1)
      {
        if (index != 0)
        {
          this.files.Move(index, 0);
          this.SaveFilesToMruFile();
        }
      }
      else
      {
        this.files.Insert(0, fileName);

        // remove excess files
        while (this.files.Count > this.MaxFilesToKeep)
          this.files.RemoveAt(this.files.Count - 1);

        this.SaveFilesToMruFile();
      }
    }

    /// <summary>
    /// Tries to open the given file
    /// <para>
    /// If successful, puts the file at the top of the list and returns true
    /// </para>
    /// <para>
    /// If unsuccessful, returns false
    /// </para>
    /// </summary>
    /// <param name="fileName"></param>
    public bool OpenFile(string fileName)
    {
      var handler = OpenFileRequested;
      if (handler == null)
        return false;

      var eventArgs = new OpenFileRequestedEventArgs(fileName);
      handler(this, eventArgs);

      if (eventArgs.Success)
        AddFile(fileName);

      return eventArgs.Success;
    }

    /// <summary>
    /// This event will be raised whenever someone calls the 'OpenFile' method
    /// </summary>
    public event EventHandler<OpenFileRequestedEventArgs> OpenFileRequested;

    #endregion
  }
}
